#ifndef	__system_H__
#define __system_H__

#include <stack>
#include <vector>
#include <map>

#include "rendVect.hpp"
#include "rendColor.hpp"
#include "rendCamera.hpp"
#include "rendBox.hpp"
#include "rendVisual.hpp"
#include "rendMaterial.hpp"

namespace rend
{

////////////////////////////////////////////////////////////////////////////////////////////////////
// Renderer state controllers
////////////////////////////////////////////////////////////////////////////////////////////////////

class Viewport
{
public:

	enum BufferBit
	{
		BUFFER_COLOR		= 1,
		BUFFER_DEPTH		= 2,
		BUFFER_STENCIL		= 4,

		BUFFER_force_long	= -1UL
	};

	Viewport();

	// rect defined as min & max corners in NDC coords
	void			setRect(const Box2& rect);
	void			setRect(const vect2& minimum, const vect2& maximum);
	const Box2&		getRect() const;

	void			setClearColor(const ColorA& color);
	const ColorA&	getClearColor() const;

	void			setClearDepth(const float depth);
	float			getClearDepth() const;

	void			setClearStencil(const unsigned stencil);
	unsigned		getClearStencil() const;

	void			setClearBuffers(const unsigned bufferMask);
	unsigned		getClearBuffers() const;

	void			actuate(const unsigned window_w,
							const unsigned window_h) const;
private:

	Box2			mRect;
	unsigned		mBufferMask;

	ColorA			mColor;
	float			mDepth;
	unsigned		mStencil;
};


class RenderTarget;

////////////////////////////////////////////////////////////////////////////////////////////////////
// Renderer - a scene-capturing renderer
//
// note: All data passed by pointer remain in ownership of the user. It is their responsibility
//		 to keep said data around and consistent till the next flush op.
////////////////////////////////////////////////////////////////////////////////////////////////////

class Renderer
{
public:
	Renderer();

	// convention:
	// some Renderer interface methods return a success flag - false for failure;
	// use the following error-reporting facility to get futher information

	enum ErrorCode
	{
		ErrorCode_NoError		= 0,
		ErrorCode_GeneralError,
		ErrorCode_InvalidQueue,
		ErrorCode_InvalidCamera,
		ErrorCode_InvalidViewport,
		ErrorCode_InvalidRenderTarget,

		// add new error codes above
		ErrorCode_count,
		ErrorCode_force_long	= -1UL
	};

	/** checks the error state and resets it to NoError
	*/
	ErrorCode	getLastError() const;

	// pixel buffer support ////////////////////////////////////////////////////////////////////////
	void		setFrameDims(const unsigned window_w,
							 const unsigned window_h);

	enum PixelFormat
	{
		PIX_Default				= 0,
		PIX_A8R8G8B8,
		PIX_X8R8G8B8,
		PIX_R8G8B8,
		PIX_A1R5G5B5,
		PIX_X1R5G5B5,
		PIX_R5G6B5,

		PIX_force_long			= -1UL
	};

	enum ZexelFormat
	{
		ZEX_None				= -1UL,
		ZEX_Default				= 0,
		ZEX_D32,
		ZEX_D24S8,
		ZEX_D16,

		ZEX_force_long			= -1UL
	};

	RenderTarget*	createRenderTarget(const unsigned width,
									   const unsigned height,
									   const PixelFormat pixelFmt,
									   const ZexelFormat zexelFmt);
	bool			deleteRenderTarget(RenderTarget* rtt);

	// context meta control
	bool			pushContext(const bool carry = false);	// saves current context, optionally carrying it over to the new one
	bool			popContext();							// restores last saved context

	// context mutators/accessors //////////////////////////////////////////////////////////////////

	void			setViewport(const Viewport* viewport);
	const Viewport*	getViewport() const;

	enum Translucency
	{
		TRANSLUCENT_AUTO		= 0,	// translucency of the group is determined by its content
		TRANSLUCENT_TRUE,				// group forced to 'translucent'
		TRANSLUCENT_FALSE				// group forced to 'opaque'
	};

	/**	opens a new aggregate drawing primitive known as 'group' -- any primitives issued through queueDraw()
		while this group is active (read: open) will be batched and treated as a single primitive in the context
		of the group's host; when used with nesting this produces a tree stucture drawn in depth-first order;

		"camera" gives the camera to be used for visualizing this group. Please, note that different cameras
		passed within one flush should share the same near and far clipping distances! Failure to comply will
		result in an undefined draw order and likely wrong occlusion and/or blending across primitives;

		"ordered" determines if any sorting logic should be ignored in favor of the order primitives are
		issued to this group;

		unordered groups automatically sort by depth: opaques first in front-to-back, followed by
		translucents in back-to-front order, respectively.
	*/
	bool			openGroup(const Camera* camera,
							  const bool ordered);

	/**	closes the current group; the parent group becomes current;

		"translucent" sets group's status with regard to translucency (see type Translucency for options);
	*/
	bool			closeGroup(const Translucency translucent = TRANSLUCENT_AUTO);

	/**	issues a draw primitive;

		visual is an object implementing the Visual interface
		material is a provider of draw parameters - shader program references and shader parameters
		world is a world-space transform
	*/
	bool			queueDraw(Visual*		visual,
							  Material*		material,
							  const matx4&	world);

	/**	shapshot of the draw queue without flushing;
	
		all groups must be closed in advance.
	*/
	bool			snapshot(RenderTarget* rtt = 0);

	/**	snapshots and flushes the draw queue;

		all groups must be closed in advance.
	*/
	bool			flush(RenderTarget* rtt = 0);

private:

	typedef	float Key;

	struct Element
	{
		void*			body;
		Material*		material;

		Element()
		: body(0)
		, material(0)
		{}

		bool isGroup() const
		{
			return material == 0;
		}
	};

	class Group : private std::multimap< Key, Element >
	{
	public:

		Group(const Camera* camera,
			  const int orderIncrement)
		: mCamera(camera)
		, mCentreSum(0, 0, 0)
		, mCentreNum(0)
		, mTranslucent(false)
		, mOrderCurrent(0)
		, mOrderIncrement(orderIncrement)
		{}

		Group(const Group& source);
		~Group();

		// note: any group updates work only on open groups!

		bool add(Group*			group);

		bool add(Visual*		visual,
				 Material*		material,
				 const matx4&	world);

		void setTranslucency(const bool translucent);

		void snapshot();

		bool isEmpty() const
		{
			return empty();
		}

	private:

		const Camera*	mCamera;
		vect3			mCentreSum;
		int				mCentreNum;
		bool			mTranslucent;
		int				mOrderCurrent;
		int				mOrderIncrement;
	};

	typedef std::vector< Group* >	GroupStack;

	struct Context
	{
		const Viewport*	vport;
		Group*			root;
		GroupStack		openStack;
	};

	typedef std::stack< Context, std::vector< Context > >	ContextStack;

	ContextStack			mContextStack;

	unsigned				mWindowW;
	unsigned				mWindowH;

	mutable ErrorCode		mError;

	static const Viewport	defaultViewport;
};

} // namespace rend

#endif // __system_H__
